#pragma once

// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT License

// Vec3_.h : interface of the Vec3_ class
//

#include "alg3d/Real.h"
#include "alg3d/Common.h"
#include <iostream>
#include <cassert>
#include <cmath>

namespace gx
{

template<class T>
class Vec4_;

// --------------------------------------------------------------
// "Vec3_" class
// --------------------------------------------------------------
// Represents a point or direction in Euclidean space
// --------------------------------------------------------------

template<class T>
class Vec3_
{
public:

    // ----------------------------------------------------------
    // Member variables (public on purpose)
    // ----------------------------------------------------------

    T x, y, z;
    
    // ----------------------------------------------------------
    // Create an unitialized vector
    // ----------------------------------------------------------

    Vec3_();

    // ----------------------------------------------------------
    // Creates a zero vector. Example of use:
    //   gx::Vec3_ v(gx::zero);
    // ----------------------------------------------------------

    explicit Vec3_(Zero);

    // ----------------------------------------------------------
    // Creates a vector from a array (which must be at least
    // of size 3)
    // ----------------------------------------------------------

    explicit Vec3_(const T * val);

    // ----------------------------------------------------------
    // Create a vector from its individual components
    // ----------------------------------------------------------

    Vec3_(T x, T y, T z=0);

    // ----------------------------------------------------------
    // Create a 3D vector from a homogeneous vector (drops the
    // w component)
    // ----------------------------------------------------------

    // Defined in Vec4.h to prevent cyclic dependency
    Vec3_(const Vec4_<T> & v4);

    // ----------------------------------------------------------
    // Make a vector unit-length. The second version returns
    // the original length
    // ----------------------------------------------------------

    Vec3_ & normalize();
    Vec3_ & normalize(double & norm_);
    
    // ----------------------------------------------------------
    // Indexed access to the vector components (useful for loops)
    // ----------------------------------------------------------

    T & operator[](int i);
    const T & operator [](int i) const;

    // ----------------------------------------------------------
    // Return a pointer to the first component of a vector
    // ----------------------------------------------------------

    T * to_array();
    const T * to_array() const;

    // ----------------------------------------------------------
    // Converts this vector to a vector of a different type 
    // (e.g., float => double)
    // ----------------------------------------------------------

    template<class OtherT>
    Vec3_<OtherT> asVec3() const;

    // ----------------------------------------------------------
    // Copies the components of this vector to an array
    // ----------------------------------------------------------

    T * to_array(T * dest) const;

    // ----------------------------------------------------------
    // Compares two vectors for equality
    // ----------------------------------------------------------
    
    bool equals(const Vec3_ & vec, T tolerance = 1e-8) const;

    // ----------------------------------------------------------
    // Tells whether a vector is the zero vector
    // ----------------------------------------------------------

    bool is_zero(T tolerance = 1e-8) const;

    // ----------------------------------------------------------
    // Tells whether a vector is unit-length
    // ----------------------------------------------------------

    bool is_unit_length(T tolerance = 1e-8) const;

    // ----------------------------------------------------------
    // Compute the norm and the squared norm of a vector
    // ----------------------------------------------------------

    T norm() const;
    T norm2() const;

    // ----------------------------------------------------------
    // Compute the distance (and the squared distance) 
    // between two vectors
    // ----------------------------------------------------------

    T dist(const Vec3_ & p) const;
    T dist2(const Vec3_ & p) const;

    // ----------------------------------------------------------
    // Compute the dot and cross products of two vectors
    // ----------------------------------------------------------

    T dot(const Vec3_ & v) const;
    Vec3_ cross(const Vec3_ & v) const;
};

// --------------------------------------------------------------
// I/O functions to perform IO with a Vec3_
// --------------------------------------------------------------

template<class T>
std::ostream & operator<<(std::ostream & os, const Vec3_<T>& val);

template<class T>
std::istream & operator>>(std::istream & is, Vec3_<T>& val);

// --------------------------------------------------------------
// Convenience typedefs
// --------------------------------------------------------------

typedef Vec3_<Real> Vec3;
typedef Vec3_<double> Vec3d;
typedef Vec3_<float> Vec3f;

// --------------------------------------------------------------
// Overloaded operators 
// --------------------------------------------------------------

// Operations between vectors
template<class T>
Vec3_<T> operator-(const Vec3_<T> & u);

template<class T>
Vec3_<T>& operator+=(Vec3_<T> & lhs, const Vec3_<T> & rhs);

template<class T>
Vec3_<T>& operator-=(Vec3_<T> & lhs, const Vec3_<T> & rhs);

template<class T>
Vec3_<T> operator+(const Vec3_<T> & lhs, const Vec3_<T> & rhs);

template<class T>
Vec3_<T> operator-(const Vec3_<T> & lhs, const Vec3_<T> & rhs);

// Operations between a vector and a scalar

template<class T, class S>
Vec3_<T>& operator*=(Vec3_<T> & lhs, S s);

template<class T, class S>
Vec3_<T>& operator/=(Vec3_<T> & lhs, S s);

template<class T, class S>
Vec3_<T> operator*(const Vec3_<T> & v, S s);

template<class T, class S>
Vec3_<T> operator*(S s, const Vec3_<T> & v);

template<class T, class S>
Vec3_<T> operator/(const Vec3_<T> & v, S s);

// --------------------------------------------------------------
// Three "dummy" types and their associated global variables. 
// They will allow us to write things like 
//      gx::Vec3_ v = gx::I; 
// instead of
//      gx::Vec3_ v = gx::Vec3_(1., 0., 0.); 
// --------------------------------------------------------------

extern Vec3 I;
extern Vec3 J;
extern Vec3 K;

// --------------------------------------------------------------
// Implementation of some member functions whose speed we deem 
// critical (basically except those involving I/O)
// --------------------------------------------------------------

template<class T> 
Vec3_<T>::Vec3_()
{
}

template<class T> 
Vec3_<T>::Vec3_(Zero const) 
: x(0), y(0), z(0)
{ 
} 

template<class T> 
Vec3_<T>::Vec3_(T const * val)
: x(val[0]), y(val[1]), z(val[2])
{
}

template<class T>
Vec3_<T>::Vec3_(T x, T y, T z) 
: x(x), y(y), z(z)
{
}

// ------------------------------------------------------------------------

template<class T> 
T& Vec3_<T>::operator[](int i) 
{ 
    return (&x)[i]; 
}

template<class T> 
const T& Vec3_<T>::operator[](int i) const 
{ 
    return (&x)[i]; 
}

// ------------------------------------------------------------------------

template<class T>
T* Vec3_<T>::to_array(T* dest) const 
{
    dest[0] = x;
    dest[1] = y;
    dest[2] = z;
    return dest;
}

template<class T> 
T* Vec3_<T>::to_array() 
{
    return &x; 
}

template<class T> 
const T* Vec3_<T>::to_array() const 
{
    return &x; 
}

// ------------------------------------------------------------------------

template<class T> 
bool Vec3_<T>::equals(const Vec3_ & vec, T tolerance) const
{
    const T dx = x - vec.x, dy = y - vec.y, dz = z - vec.z;
    return dx * dx + dy * dy + dz * dz < tolerance * tolerance;
}

template<class T> 
bool Vec3_<T>::is_zero(T tolerance) const
{
    return norm2() < tolerance * tolerance;
}

template<class T> 
bool Vec3_<T>::is_unit_length(T tolerance) const 
{
    return fabs(norm() - 1.0) < tolerance;
}

// ------------------------------------------------------------------------

template<class T>
template<class OtherT>
Vec3_<OtherT> Vec3_<T>::asVec3() const
{
    return Vec3_<OtherT>(OtherT(x), OtherT(y), OtherT(z));
}

// ------------------------------------------------------------------------


template<class T> 
Vec3_<T> operator-(const Vec3_<T> & u) 
{
    return Vec3_<T>(-u.x, -u.y, -u.z);
}

template<class T> 
Vec3_<T>& operator+=(Vec3_<T> &lhs, const Vec3_<T> &rhs)
{
    lhs.x += rhs.x;lhs.y += rhs.y;lhs.z += rhs.z; return lhs;
}

template<class T> 
Vec3_<T>& operator-=(Vec3_<T> &lhs, const Vec3_<T> &rhs)
{
    lhs.x -= rhs.x;lhs.y -= rhs.y;lhs.z -= rhs.z; return lhs;
}

template<class T, class S> 
Vec3_<T>& operator*=(Vec3_<T> &lhs, S s)
{
    lhs.x *= s;lhs.y *= s;lhs.z *= s; return lhs;
}

template<class T, class S> 
Vec3_<T>& operator/=(Vec3_<T> &lhs, S s)
{
    return lhs *= (1 / s);
}

template<class T> 
Vec3_<T> operator+(const Vec3_<T> & v1, const Vec3_<T> & v2)
{
    return Vec3_<T>(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}

template<class T> 
Vec3_<T> operator-(const Vec3_<T> & v1, const Vec3_<T> & v2)
{
    return Vec3_<T>(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}

template<class T, class S> 
Vec3_<T> operator*(const Vec3_<T> & v, S s)
{
    return Vec3_<T>(v.x * s, v.y * s, v.z * s);
}

template<class T, class S> 
Vec3_<T> operator*(S s, const Vec3_<T> & v)
{
    return v * s;
}

template<class T, class S> 
Vec3_<T> operator/(const Vec3_<T> & v, S s)
{
    return v * (1 / s);
}

// ------------------------------------------------------------------------

template<class T> 
T Vec3_<T>::dist(const Vec3_ & p) const
{
    return (*this - p).norm();
}

template<class T> 
T Vec3_<T>::dist2(const Vec3_ & p) const
{
    return (*this - p).norm2();
}

template<class T> 
T Vec3_<T>::norm2() const
{
    return dot(*this);
}

template<class T> 
T Vec3_<T>::norm() const
{
    return sqrt(norm2());
}

template<class T> 
Vec3_<T> & Vec3_<T>::normalize() 
{
    T norm_;
    return normalize(norm_);
}

template<class T> 
Vec3_<T> & Vec3_<T>::normalize(double & norm_) 
{
    norm_ = norm();
    assert(norm_ > 1e-8);
    *this /= norm_;
    return *this;
}

template<class T> 
T Vec3_<T>::dot(const Vec3_ & v) const
{
    return x * v.x + y * v.y + z * v.z; 
}

template<class T> 
Vec3_<T> Vec3_<T>::cross(const Vec3_ & v) const 
{
    return Vec3_(y * v.z - z * v.y,
               -x * v.z + z * v.x,
                x * v.y - y * v.x);
}


template<class T>
std::ostream & operator<<(std::ostream &os, const gx::Vec3_<T> &val)
{
    const bool is_console = &os == &std::cout;
    if(is_console) 
        os << "(";
    int i;
    for (i=0; i < 3; ++i)
    {
        os << zero_if_tiny(val[i]);
        if(is_console)
            os << ( i < 2 ? ",":")" );
        else
            os << ( i < 2 ? " ":"" );
    }
    return os;
}

template<class T> 
std::istream & operator>>(std::istream & is, Vec3_<T> & val)
{
    int i;
    for (i=0; i < 3; ++i)
    {
        is >> val[i];
    }
    return is;
}

// ------------------------------------------------------------------------

}


